<?php
/**
 * DAO for {@link Category}.
 * <p>
 * It is also a service, ideally, this class should be divided into DAO and Service.
 */
final class CategoryDao {

    /** @var PDO */
    private $db = null;

    public function __destruct() {
        // close db connection
        $this->db = null;
    }

    /**
     * Find {@link Category} by identifier.
     * @return Category Category or <i>null</i> if not found
     */
    public function findById($id) {
        $row = $this->query('SELECT * FROM categories WHERE id = ' . (int) $id)->fetch();
        if (!$row) {
            return null;
        }
        $category = new Category();
        CategoryMapper::map($category, $row);
        return $category;
    }

    /**
     * Find {@link Category} by identifier.
     * @return Category Category or <i>null</i> if not found
     */
    public function findByName($name) {
        $row = $this->query('SELECT * FROM categories WHERE name = ' . (string) $name)->fetch();
        if (!$row) {
            return null;
        }
        $category = new Category();
        CategoryMapper::map($category, $row);
        return $category;
    }
    
    /**
     * Find all {@link Content}s by search criteria.
     * @return array array of {@link Content}s
     */
    public function listAll() {
        $result = array();
        foreach ($this->query('SELECT * FROM categories ORDER BY name') as $row) {
            $category = new Category();
            CategoryMapper::map($category, $row);
            $result[$category->getId()] = $category;
        }
        return $result;
    }

    /**
     * Save {@link Category}.
     * @param Category $category {@link Category} to be saved
     * @return Category saved {@link Category} instance
     */
    public function save(Category $category) {
        if ($category->getId() === null) {
            return $this->insert($category);
        }
        return $this->update($category);
    }

    /**
     * Delete {@link Category} by identifier.
     * @param int $id {@link Category} identifier
     * @return bool <i>true</i> on success, <i>false</i> otherwise
     */
    public function delete($id) {
        $sql = '
            DELETE FROM categories
            WHERE id = :id';
        $statement = $this->getDb()->prepare($sql);
        $this->executeStatement($statement, array(
            ':id' => $id,
        ));
        return $statement->rowCount() == 1;
    }

    /**
     * @return PDO
     */
    private function getDb() {
        if ($this->db !== null) {
            return $this->db;
        }
        $config = Config::getConfig("db");
        try {
            $this->db = new PDO($config['dsn'], $config['username'], $config['password']);
        } catch (Exception $ex) {
            throw new Exception('DB connection error: ' . $ex->getMessage());
        }
        return $this->db;
    }

    /**
     * @return Category
     * @throws Exception
     */
    private function insert(Category $category) {
        $category->setId(null);
        $sql = '
            INSERT INTO categories (id, name) VALUES (:id, :name)';
        return $this->execute($sql, $category);
    }

    /**
     * @return Category
     * @throws Exception
     */
    private function update(Category $category) {
        $category->setLastModifiedOn(new DateTime());
        $sql = '
            UPDATE categories
            SET name = :name
            WHERE id = :id';
        return $this->execute($sql, $category);
    }

    /**
     * @return Category
     * @throws Exception
     */
    private function execute($sql, Category $category) {
        $statement = $this->getDb()->prepare($sql);
        $this->executeStatement($statement, $this->getParams($category));
        if (!$category->getId()) {
            return $this->findById($this->getDb()->lastInsertId());
        }
        if (!$statement->rowCount()) {
            throw new NotFoundException('Category with ID "' . $category->getId() . '" does not exist.');
        }
        return $category;
    }

    private function getParams(Category $category) {
        $params = array(
            ':id' => $category->getId(),
            ':name' => $category->getName(),
        );
        return $params;
    }

    private function executeStatement(PDOStatement $statement, array $params) {
        if (!$statement->execute($params)) {
            self::throwDbError($this->getDb()->errorInfo());
        }
    }

    /**
     * @return PDOStatement
     */
    private function query($sql) {
        $statement = $this->getDb()->query($sql, PDO::FETCH_ASSOC);
        if ($statement === false) {
            self::throwDbError($this->getDb()->errorInfo());
        }
        return $statement;
    }

    private static function throwDbError(array $errorInfo) {
        // TODO log error, send email, etc.
        throw new Exception('DB error [' . $errorInfo[0] . ', ' . $errorInfo[1] . ']: ' . $errorInfo[2]);
    }

}

?>
